// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包smtp实现RFC 5321中定义的简单邮件传输协议。
// 它还实现了以下扩展：
// 8BITMIME RFC 1652 
// AUTH RFC 2554 
// STARTTLS RFC 3207 
// 其他扩展可以由客户端处理。
// 
// smtp包已冻结，不接受新功能。
// 一些外部软件包提供更多功能。参见：
// 
// https:
package smtp

import (
	"crypto/tls"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"net"
	"net/textproto"
	"strings"
)

// 客户端表示到SMTP服务器的客户端连接。
type Client struct {
	// Text是客户端使用的textproto.Conn。导出它是为了允许
	// 客户端添加扩展。
	Text *textproto.Conn
	// 保留对连接的引用，以便用于创建TLS 
	// 稍后的连接
	conn net.Conn
	// 客户端是否使用TLS 
	tls        bool
	serverName string
	// 支持的扩展映射
	ext map[string]string
	// 支持的身份验证机制
	auth       []string
	localName  string // 在HELO/EHLO 
	helloError error  // 来自hello 
}

// 拨号的错误返回一个连接到addr的SMTP服务器的新客户端。
// 地址必须包含一个端口，如“mail.example.com:smtp”中所示。
func Dial(addr string) (*Client, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	host, _, _ := net.SplitHostPort(addr)
	return NewClient(conn, host)
}

// NewClient返回使用现有连接和主机作为身份验证时要使用的服务器名称的新客户端。
func NewClient(conn net.Conn, host string) (*Client, error) {
	text := textproto.NewConn(conn)
	_, _, err := text.ReadResponse(220)
	if err != nil {
		text.Close()
		return nil, err
	}
	c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
	_, c.tls = conn.(*tls.Conn)
	return c, nil
}

// 关闭关闭连接。
func (c *Client) Close() error {
	return c.Text.Close()
}

// 如果需要，hello将运行hello交换。
func (c *Client) hello() error {
	if !c.didHello {
		c.didHello = true
		err := c.ehlo()
		if err != nil {
			c.helloError = c.helo()
		}
	}
	return c.helloError
}

// Hello将HELO或EHLO作为给定主机名发送到服务器。
// 仅当客户端需要控制所使用的主机名时才需要调用此方法。客户端将自动将自己介绍为“localhost”
// 否则。如果调用了Hello，则必须在
// 任何其他方法之前调用它。
func (c *Client) Hello(localName string) error {
	if err := validateLine(localName); err != nil {
		return err
	}
	if c.didHello {
		return errors.New("smtp: Hello called after other methods")
	}
	c.localName = localName
	return c.hello()
}

// cmd是一个方便的函数，它发送命令并返回响应
func (c *Client) cmd(expectCode int, format string, args ...interface{}) (int, string, error) {
	id, err := c.Text.Cmd(format, args...)
	if err != nil {
		return 0, "", err
	}
	c.Text.StartResponse(id)
	defer c.Text.EndResponse(id)
	code, msg, err := c.Text.ReadResponse(expectCode)
	return code, msg, err
}

// helo向服务器发送helo问候语。仅当
// 服务器不支持ehlo时才应使用。
func (c *Client) helo() error {
	c.ext = nil
	_, _, err := c.cmd(250, "HELO %s", c.localName)
	return err
}

// ehlo向服务器发送ehlo（扩展hello）问候语。它
// 应该是支持它的服务器的首选问候语。
func (c *Client) ehlo() error {
	_, msg, err := c.cmd(250, "EHLO %s", c.localName)
	if err != nil {
		return err
	}
	ext := make(map[string]string)
	extList := strings.Split(msg, "\n")
	if len(extList) > 1 {
		extList = extList[1:]
		for _, line := range extList {
			args := strings.SplitN(line, " ", 2)
			if len(args) > 1 {
				ext[args[0]] = args[1]
			} else {
				ext[args[0]] = ""
			}
		}
	}
	if mechs, ok := ext["AUTH"]; ok {
		c.auth = strings.Split(mechs, " ")
	}
	c.ext = ext
	return err
}

// StartTLS发送StartTLS命令并加密所有进一步的通信。
// 只有播发STARTTLS扩展的服务器才支持此功能。
func (c *Client) StartTLS(config *tls.Config) error {
	if err := c.hello(); err != nil {
		return err
	}
	_, _, err := c.cmd(220, "STARTTLS")
	if err != nil {
		return err
	}
	c.conn = tls.Client(c.conn, config)
	c.Text = textproto.NewConn(c.conn)
	c.tls = true
	return c.ehlo()
}

// TLS连接状态返回客户端的TLS连接状态。
// 如果StartTLS执行
// 未成功，则返回值为其零值。
func (c *Client) TLSConnectionState() (state tls.ConnectionState, ok bool) {
	tc, ok := c.conn.(*tls.Conn)
	if !ok {
		return
	}
	return tc.ConnectionState(), true
}

// 验证检查服务器上电子邮件地址的有效性。
// 如果Verify返回nil，则地址有效。非零返回
// 不一定表示地址无效。出于安全原因，许多服务器
// 不会验证地址。
func (c *Client) Verify(addr string) error {
	if err := validateLine(addr); err != nil {
		return err
	}
	if err := c.hello(); err != nil {
		return err
	}
	_, _, err := c.cmd(250, "VRFY %s", addr)
	return err
}

// Auth使用提供的身份验证机制对客户端进行身份验证。
// 身份验证失败将关闭连接。
// 只有公布身份验证扩展的服务器才支持此功能。
func (c *Client) Auth(a Auth) error {
	if err := c.hello(); err != nil {
		return err
	}
	encoding := base64.StdEncoding
	mech, resp, err := a.Start(&ServerInfo{c.serverName, c.tls, c.auth})
	if err != nil {
		c.Quit()
		return err
	}
	resp64 := make([]byte, encoding.EncodedLen(len(resp)))
	encoding.Encode(resp64, resp)
	code, msg64, err := c.cmd(0, strings.TrimSpace(fmt.Sprintf("AUTH %s %s", mech, resp64)))
	for err == nil {
		var msg []byte
		switch code {
		case 334:
			msg, err = encoding.DecodeString(msg64)
		case 235:
			// 最后一封邮件不是base64，因为它不是质询
			msg = []byte(msg64)
		default:
			err = &textproto.Error{Code: code, Msg: msg64}
		}
		if err == nil {
			resp, err = a.Next(msg, code == 334)
		}
		if err != nil {
			// 中止身份验证
			c.cmd(501, "*")
			c.Quit()
			break
		}
		if resp == nil {
			break
		}
		resp64 = make([]byte, encoding.EncodedLen(len(resp)))
		encoding.Encode(resp64, resp)
		code, msg64, err = c.cmd(0, string(resp64))
	}
	return err
}

// 邮件使用提供的电子邮件地址向服务器发出邮件命令。
// 如果服务器支持8BITMIME扩展，Mail会添加BODY=8BITMIME 
// 参数。如果服务器支持SMTPUTF8扩展，Mail会添加
// SMTPUTF8参数。
// 这将启动一个邮件事务，然后是一个或多个Rcpt调用。
func (c *Client) Mail(from string) error {
	if err := validateLine(from); err != nil {
		return err
	}
	if err := c.hello(); err != nil {
		return err
	}
	cmdStr := "MAIL FROM:<%s>"
	if c.ext != nil {
		if _, ok := c.ext["8BITMIME"]; ok {
			cmdStr += " BODY=8BITMIME"
		}
		if _, ok := c.ext["SMTPUTF8"]; ok {
			cmdStr += " SMTPUTF8"
		}
	}
	_, _, err := c.cmd(250, cmdStr, from)
	return err
}

// Rcpt使用提供的电子邮件地址向服务器发出Rcpt命令。
// 对Rcpt的调用必须在对邮件的调用之前进行，并且可以在调用之后进行
// 数据调用或另一个Rcpt调用。
func (c *Client) Rcpt(to string) error {
	if err := validateLine(to); err != nil {
		return err
	}
	_, _, err := c.cmd(25, "RCPT TO:<%s>", to)
	return err
}

type dataCloser struct {
	c *Client
	io.WriteCloser
}

func (d *dataCloser) Close() error {
	d.WriteCloser.Close()
	_, _, err := d.c.Text.ReadResponse(250)
	return err
}

// Data向服务器发出数据命令，并返回一个writer，该writer可用于写入邮件标题和正文。调用方应该
// 在调用c上的任何其他方法之前关闭编写器。对
// 数据的调用之前必须有一个或多个对Rcpt的调用。
func (c *Client) Data() (io.WriteCloser, error) {
	_, _, err := c.cmd(354, "DATA")
	if err != nil {
		return nil, err
	}
	return &dataCloser{c, c.Text.DotWriter()}, nil
}

var testHookStartTLS func(*tls.Config) // 无，除了测试

// SendMail连接到addr的服务器，如果
// 可能，则切换到TLS，如果可能，使用可选机制a进行身份验证，
// 然后使用
// 消息消息消息。
// 地址必须包含一个端口，如“mail.example.com:smtp”中所示。
// 
// to参数中的地址是SMTP RCPT地址。
// 
// msg参数应为RFC 822样式的电子邮件，邮件头为
// 首先为空行，然后为邮件正文。msg 
// 的行应以CRLF结尾。消息头通常应包括
// 字段，如“发件人”、“收件人”、“主题”和“抄送”。发送“Bcc”
// 消息的方法是在to 
// 参数中包含电子邮件地址，但不将其包含在消息头中。
// 
// SendMail函数和net/smtp包是低级
// 机制，不支持DKIM签名、MIME 
// 附件（请参阅MIME/多部分包）或其他邮件
// 功能。标准
// 库之外存在更高级别的包。
func SendMail(addr string, a Auth, from string, to []string, msg []byte) error {
	if err := validateLine(from); err != nil {
		return err
	}
	for _, recp := range to {
		if err := validateLine(recp); err != nil {
			return err
		}
	}
	c, err := Dial(addr)
	if err != nil {
		return err
	}
	defer c.Close()
	if err = c.hello(); err != nil {
		return err
	}
	if ok, _ := c.Extension("STARTTLS"); ok {
		config := &tls.Config{ServerName: c.serverName}
		if testHookStartTLS != nil {
			testHookStartTLS(config)
		}
		if err = c.StartTLS(config); err != nil {
			return err
		}
	}
	if a != nil && c.ext != nil {
		if _, ok := c.ext["AUTH"]; !ok {
			return errors.New("smtp: server doesn't support AUTH")
		}
		if err = c.Auth(a); err != nil {
			return err
		}
	}
	if err = c.Mail(from); err != nil {
		return err
	}
	for _, addr := range to {
		if err = c.Rcpt(addr); err != nil {
			return err
		}
	}
	w, err := c.Data()
	if err != nil {
		return err
	}
	_, err = w.Write(msg)
	if err != nil {
		return err
	}
	err = w.Close()
	if err != nil {
		return err
	}
	return c.Quit()
}

// 扩展报告服务器是否支持扩展。
// 扩展名不区分大小写。如果支持该扩展，
// 扩展还将返回一个字符串，该字符串包含
// 服务器为扩展指定的任何参数。
func (c *Client) Extension(ext string) (bool, string) {
	if err := c.hello(); err != nil {
		return false, ""
	}
	if c.ext == nil {
		return false, ""
	}
	ext = strings.ToUpper(ext)
	param, ok := c.ext[ext]
	return ok, param
}

// 重置将RSET命令发送到服务器，中止当前邮件
// 事务。
func (c *Client) Reset() error {
	if err := c.hello(); err != nil {
		return err
	}
	_, _, err := c.cmd(250, "RSET")
	return err
}

// Noop向服务器发送Noop命令。它只检查
// 与服务器的连接是否正常。
func (c *Client) Noop() error {
	if err := c.hello(); err != nil {
		return err
	}
	_, _, err := c.cmd(250, "NOOP")
	return err
}

// Quit发送Quit命令并关闭与服务器的连接。
func (c *Client) Quit() error {
	if err := c.hello(); err != nil {
		return err
	}
	_, _, err := c.cmd(221, "QUIT")
	if err != nil {
		return err
	}
	return c.Text.Close()
}

// 根据RFC 5321 
func validateLine(line string) error {
	if strings.ContainsAny(line, "\n\r") {
		return errors.New("smtp: A line must not contain CR or LF")
	}
	return nil
}
